//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
matrix WorldViewProjection;
const float fExposure = -2.0;
float3 v3SunDir = float3( 0, 1, 0 );

cbuffer cbConstant
{
	float PI = 3.141592653;
	float InnerRadius = 6356.7523142;
	float OuterRadius = 6356.7523142 * 1.015;
	float fScale = 1.0 / (6452.103598913 - 6356.7523142);
	float KrESun = 0.0025 * 30.0;
	float KmESun = 0.0010 * 30.0;
	float Kr4PI = 0.0025 * 4.0 * 3.141592653;
	float Km4PI = 0.0010 * 4.0 * 3.141592653;
	int tNumSamples = 50;
	int iNumSamples = 20;
	float2 v2dRayleighMieScaleHeight= float2( 0.25, 0.1 );
	float3 WavelengthMie = float3( pow( 0.650, -0.84 ), pow( 0.570, -0.84 ), pow( 0.475, -0.84 ) );
	float InvOpticalDepthN = 1.0 / 128.0;
	float3 v3HG = float3( 1.5f * ( (1.0f - (-0.995*-0.995)) / (2.0f + (-0.995*-0.995)) ), 1.0f + (-0.995*-0.995), 2.0f * -0.995 );
	float InvOpticalDepthNLessOne = 1.0 / ( 128.0 - 1.0 );
	float2 InvRayleighMieN = float2( 1.0 / 128.0, 1.0 / 64.0 );
	float2 InvRayleighMieNLessOne = float2( 1.0 / (128.0 - 1.0), 1.0 / (64.0 - 1.0) );
	float HalfTexelOpticalDepthN = float( 0.5 / 128.0 ); 
	float3 InvWavelength4 = float3( 1.0 / pow( 0.650, 4 ), 1.0 / pow( 0.570, 4 ), 1.0 / pow( 0.475, 4 ) );
}

//--------------------------------------------------------------------------------------
// Textures & Samplers
//--------------------------------------------------------------------------------------
Texture2D txMie, txRayleigh, txOpticalDepth;

SamplerState samLinearClamp
{
    Filter = MIN_MAG_LINEAR_MIP_POINT;
    AddressU = CLAMP;
    AddressV = CLAMP;
};	

SamplerState samLinearWrap
{
    Filter = MIN_MAG_LINEAR_MIP_POINT;
    AddressU = WRAP;
    AddressV = WRAP;
};	


//--------------------------------------------------------------------------------------
// States
//--------------------------------------------------------------------------------------


BlendState NoBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = FALSE;
};

DepthStencilState DisableDepth
{
    DepthEnable = FALSE;
    DepthWriteMask = ZERO;
};

RasterizerState DisableCulling
{
	CullMode = NONE;
};

DepthStencilState EnableDepth
{
    DepthEnable = TRUE;
    DepthWriteMask = ALL;
	DepthFunc = LESS;
};

RasterizerState EnableCulling
{
	CullMode = BACK;
};

//--------------------------------------------------------------------------------------
struct VS_INPUT
{
    float3 Pos : POSITION;
    float2 Tex0 : TEXCOORD0;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex0 : TEXCOORD0;
	float3 Tex1 : TEXCOORD1;
};

struct VS_INPUT_UPDATE
{
    float2 Pos : POSITION;
};

struct PS_INPUT_UPDATE
{
    float4 Pos : SV_POSITION;
};

struct PS_OUTPUT_UPDATE
{
    float4 RayLeigh : SV_TARGET0;
	float4 Mie : SV_TARGET1;
};


//--------------------------------------------------------------------------------------
// Atmospheric Scattering
//--------------------------------------------------------------------------------------
float3 HDR( float3 LDR)
{
	return 1.0f - exp( fExposure * LDR );
}

float3 ToneMap( float3 HDR)
{
	return (HDR / (HDR + 1.0f));
}

float getMiePhase(float fCos, float fCos2)
{
	return v3HG.x * (1.0 + fCos2) / pow(v3HG.y - v3HG.z * fCos, 1.5);
}

float getRayleighPhase(float fCos2)
{
	return 0.75 * (1.0 + fCos2);
}

float2 GetDensityRatio( float fHeight )
{
	const float fAltitude = (fHeight - InnerRadius) * fScale;
	return exp( -fAltitude / v2dRayleighMieScaleHeight.xy );
}

float HitOuterSphere( float3 O, float3 Dir ) 
{
	float3 L = -O;

	float B = dot( L, Dir );
	float C = dot( L, L );
	//if( B < 1e-5 && C > OuterRadius * OuterRadius ) 
	//	return 0;
	float D = C - B * B; 
	//if( D > OuterRadius * OuterRadius ) 
	//return 0;
	float q = sqrt( OuterRadius * OuterRadius - D );
	float t = B;
	//if( C > OuterRadius * OuterRadius ) 
	//if( C > OuterRadius * OuterRadius )  
	//	t -= q;
	//else
		t += q;

	return t;
}

float2 t( float3 P, float3 Px )
{
	// Out Scattering Integral / OpticalDepth
	float2 OpticalDepth = 0;

	float3 v3Vector =  Px - P;
	float fFar = length( v3Vector );
	float3 v3Dir = v3Vector / fFar;
			
	float fSampleLength = fFar / tNumSamples;
	float fScaledLength = fSampleLength * fScale;
	float3 v3SampleRay = v3Dir * fSampleLength;
	P += v3SampleRay * 0.5f;
			
	for(int i = 0; i < tNumSamples; i++)
	{
		float fHeight = length( P );
		OpticalDepth += GetDensityRatio( fHeight );
		P += v3SampleRay;
	}		

	OpticalDepth *= fScaledLength;
	return OpticalDepth;
}

//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
	output.Pos =  mul( float4( input.Pos, 1), WorldViewProjection );
    output.Tex0 = input.Tex0; 
    output.Tex1 = - input.Pos;
    return output;
}

PS_INPUT_UPDATE VS_UPDATE( VS_INPUT_UPDATE input )
{
    PS_INPUT_UPDATE output = (PS_INPUT_UPDATE)0;
	output.Pos = float4( input.Pos.xy, 0, 1 );
    return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS( PS_INPUT input) : SV_Target
{
	float fCos = dot( v3SunDir, input.Tex1 ) / length( input.Tex1 );
	float fCos2 = fCos * fCos;
	
	float3 v3RayleighSamples = txRayleigh.Sample( samLinearClamp, input.Tex0.xy );
	float3 v3MieSamples = txMie.Sample( samLinearClamp, input.Tex0.xy );

	float3 Color;
	Color.rgb = getRayleighPhase(fCos2) * v3RayleighSamples.rgb + getMiePhase(fCos, fCos2) * v3MieSamples.rgb;
	Color.rgb = HDR( Color.rgb );
	
	// Hack Soft Night
	//Color.rgb += max(0,(1 - Color.rgb)) * float3( 0, 0, 0.2 ); 

	return float4( Color.rgb, 1 );
}


PS_OUTPUT_UPDATE PS_UPDATE( PS_INPUT_UPDATE input) : SV_Target
{
	PS_OUTPUT_UPDATE output = (PS_OUTPUT_UPDATE)0;

	float2 Tex0 = (input.Pos - 0.5) * InvRayleighMieNLessOne.xy;
	 
	const float3 v3PointPv = float3( 0, InnerRadius + 1e-3, 0 );
	//const float dDistribution = (1.0 - exp(-0.5 * 10.0 * input.Tex0.x ));
	//const float AngleY = 100.0 * dDistribution * PI / 180.0 ;
	const float AngleY = 100.0 * Tex0.x * PI / 180.0;
	const float AngleXZ = PI * Tex0.y;
	
	float3 v3Dir;
	v3Dir.x = sin( AngleY ) * cos( AngleXZ  );
	v3Dir.y = cos( AngleY );
	v3Dir.z = sin( AngleY ) * sin( AngleXZ  );
	v3Dir = normalize( v3Dir );

	// Pv -> Pa 
	float fFarPvPa = HitOuterSphere( v3PointPv , v3Dir );
	float3 v3Ray = v3Dir;

	// In Scattering
	float3 v3PointP = v3PointPv;
	float fSampleLength = fFarPvPa / iNumSamples;
	float fScaledLength = fSampleLength * fScale;
	float3 v3SampleRay = v3Ray * fSampleLength;
	v3PointP += v3SampleRay * 0.5f;
				
	float3 v3RayleighSum = 0;
	float3 v3MieSum = 0;

	for( int k = 0; k < iNumSamples; k++ )
	{
		float PointPHeight = length( v3PointP );

		// Density Ratio at Point P
		float2 DensityRatio = GetDensityRatio( PointPHeight );
		DensityRatio *= fScaledLength;

		// P->Viewer OpticalDepth
		float2 ViewerOpticalDepth = t( v3PointP, v3PointPv );
						
		// P->Sun OpticalDepth
		float dFarPPc = HitOuterSphere( v3PointP, v3SunDir );
		float2 SunOpticalDepth = t( v3PointP, v3PointP + v3SunDir * dFarPPc );

		// Calculate the attenuation factor for the sample ray
		float2 OpticalDepthP = SunOpticalDepth.xy + ViewerOpticalDepth.xy;
		float3 v3Attenuation = exp( - Kr4PI * InvWavelength4 * OpticalDepthP.x - Km4PI * OpticalDepthP.y );

		v3RayleighSum += DensityRatio.x * v3Attenuation;
		v3MieSum += DensityRatio.y * v3Attenuation;

		// Move the position to the center of the next sample ray
		v3PointP += v3SampleRay;
	}

	float3 RayLeigh = v3RayleighSum * KrESun;
	float3 Mie = v3MieSum * KmESun;
	RayLeigh *= InvWavelength4;
	Mie *= WavelengthMie;
	
	output.RayLeigh = float4( RayLeigh, 1 );
	output.Mie = float4( Mie, 1 );
	return output;
}


PS_OUTPUT_UPDATE PS_UPDATE_FAST( PS_INPUT_UPDATE input) : SV_Target
{
	PS_OUTPUT_UPDATE output = (PS_OUTPUT_UPDATE)0;

	float2 Tex0 = (input.Pos - 0.5) * InvRayleighMieNLessOne.xy; // [0..1]
	float2 Tex1 = (input.Pos - 0.5) * InvRayleighMieN.xy + HalfTexelOpticalDepthN; // [0.5/Texsize .. (Texsize-0.5)/Texsize]
	const float3 v3PointPv = float3( 0, InnerRadius + 1e-3, 0 );
	
	//const float dDistribution = (1.0 - exp(-0.5 * 10.0 * Tex0.x ));
	//const float AngleY = 100.0 * dDistribution * PI / 180.0;
	const float AngleY = 100.0 * Tex0.x * PI / 180.0;// [0..100]
	//const float AngleXZ = (PI * 2.0) * Tex0.y;
	const float AngleXZ = PI * Tex0.y;// [0..PI]	
	
	float3 v3Dir;
	v3Dir.x = sin( AngleY ) * cos( AngleXZ  );
	v3Dir.y = cos( AngleY );
	v3Dir.z = sin( AngleY ) * sin( AngleXZ  );
	v3Dir = normalize( v3Dir );

	// Pv -> Pa 
	float fFarPvPa = HitOuterSphere( v3PointPv , v3Dir );
	float3 v3Ray = v3Dir;

	// In Scattering
	float3 v3PointP = v3PointPv;
	float fSampleLength = fFarPvPa / iNumSamples;
	float fScaledLength = fSampleLength * fScale;
	float3 v3SampleRay = v3Ray * fSampleLength;
	v3PointP += v3SampleRay * 0.5f;
				
	float3 v3RayleighSum = 0;
	float3 v3MieSum = 0;
	float SampleU = HalfTexelOpticalDepthN;

	float2 Mies = 0;
	for( int k = 0; k < iNumSamples; k++ )
	{
		float PointPHeight = length( v3PointP );

		// Density Ratio at Point P
		float2 DensityRatio = GetDensityRatio( PointPHeight );
		DensityRatio *= fScaledLength;

		// P->Viewer OpticalDepth
	//	float2 ViewerOpticalDepth2 = t( v3PointP, v3PointPv );
		float2 ViewerOpticalDepth = txOpticalDepth.Sample( samLinearClamp, float2( Tex1.x, SampleU ) ).rg;
/*
		float2 diff = abs( ViewerOpticalDepth2 - ViewerOpticalDepth ); 

		if ( diff.x > 1e-2 )
			clip( -1 );

		if ( diff.y > 1e-2 )
			clip( -1 );
*/
		// P->Sun OpticalDepth
		//float dFarPPc = HitOuterSphere( v3PointP, v3SunDir );
		//float2 SunOpticalDepth2 = t( v3PointP, v3PointP + v3SunDir * dFarPPc );

		float fAngle = dot(v3PointP, v3SunDir) / length(v3PointP);
//		fdegree = (acos( fAngle ) / ((10.0/18.0) * PI));

		//float index = (1.0 - exp(-0.5 * 10.0 * ((1.0 - fAngle) * 9.0/10.0) ));
		float index = ((1.0 - fAngle) * 9.0/ 10.0) * 127.0/128.0;
		//0.5f - fCameraAngle * 0.5f
		//float index = (0.5 - fAngle * 0.5);
		float2 SunOpticalDepth = txOpticalDepth.Sample( samLinearClamp, float2( index, SampleU ) ).ba;
/*
		float2 diff = abs( SunOpticalDepth2 - SunOpticalDepth ); 

		if ( diff.x > 2 )
			SunOpticalDepth = -1e6;//clip( -1 );
			
		if ( diff.y > 2 )
			SunOpticalDepth = -1e6;//clip( -1 );
*/
		//Mies += SunOpticalDepth;
		
		// Calculate the attenuation factor for the sample ray
		float2 OpticalDepthP = SunOpticalDepth.xy + ViewerOpticalDepth.xy;
		float3 v3Attenuation = exp( - Kr4PI * InvWavelength4 * OpticalDepthP.x - Km4PI * OpticalDepthP.y );

		v3RayleighSum += DensityRatio.x * v3Attenuation;
		v3MieSum += DensityRatio.y * v3Attenuation;

		// Move the position to the center of the next sample ray
		v3PointP += v3SampleRay;
		SampleU += 1.0f / float(iNumSamples);
	}

	float3 RayLeigh = v3RayleighSum * KrESun;
	float3 Mie = v3MieSum * KmESun;
	RayLeigh *= InvWavelength4;
	Mie *= WavelengthMie;
	
	output.RayLeigh = float4( RayLeigh, 1 );
	output.Mie = float4( Mie, 1 );
	return output;
}


float4 PS_CREATE_OPTICALDEPTH( PS_INPUT_UPDATE input) : SV_Target0
{
	float2 Tex0 = (input.Pos - 0.5) * float2( InvOpticalDepthNLessOne, InvOpticalDepthN ); // [0..1] x [0..127.0/128.0]
	float3 v3PointPv = float3( 0, InnerRadius + 1e-3, 0 );
	
	//const float dDistribution = (1.0 - exp(-0.5 * 10.0 * Tex0.x ));
	//const float AngleY = 100.0 * dDistribution * PI / 180.0 ;
	float AngleY = 100.0 * Tex0.x * PI / 180.0; // [0..rad(100)]
	
	float3 v3Ray;
	v3Ray.x = sin( AngleY );
	v3Ray.y = cos( AngleY );
	v3Ray.z = 0;

	// Pv -> Pa 
	float fFarPvPa = HitOuterSphere( v3PointPv , v3Ray );

	// In Scattering
	float3 v3PointP = v3PointPv;
	float fSampleLength = fFarPvPa / iNumSamples;
	float3 v3SampleRay = v3Ray * fSampleLength;
	
	v3PointP += v3SampleRay * 0.5f;
	v3PointP += v3SampleRay * ( Tex0.y * iNumSamples );// [0.5 * SampleRay .. iNumSample * SampleRay]

	// P->Viewer OpticalDepth
	float2 ViewerOpticalDepth = t( v3PointP, v3PointPv );
				
	// P->Sun OpticalDepth
/*
	// As the y tex coord goes from 0 to 1, the angle goes from 0 to 180 degrees
	//float fCos = 1.0 - ( Tex0.x * 2.0 );
	float fAngle = acos( 1.0 - ( Tex0.x * 2.0 ) );
	v3Ray.x = sin( fAngle );
	v3Ray.y = cos( fAngle );
	v3Ray.z = 0;

	v3PointPv += (OuterRadius - InnerRadius) * Tex0.y;
	float fFarPPc = HitOuterSphere( v3PointPv, v3Ray );
	fSampleLength = fFarPPc / iNumSamples;
	v3SampleRay = v3Ray * fSampleLength;
	
	v3PointP = v3PointPv;
	v3PointP += v3SampleRay * 0.5f;
	//v3PointP += v3SampleRay * ( Tex0.y * iNumSamples );
*/
    float2 SunOpticalDepth = t( v3PointP, v3PointPv + v3Ray * fFarPvPa ) ;

	return float4( ViewerOpticalDepth.xy, SunOpticalDepth.xy );
}


//--------------------------------------------------------------------------------------
technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
		
		SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( EnableDepth, 0 );
        SetRasterizerState( EnableCulling );
    }
}

technique10 Update
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS_UPDATE() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS_UPDATE() ) );
		
		SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( DisableDepth, 0 );
        SetRasterizerState( DisableCulling );
    }
}

technique10 Create_Optical_Depth
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS_UPDATE() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS_CREATE_OPTICALDEPTH() ) );
		
		SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( DisableDepth, 0 );
        SetRasterizerState( DisableCulling );
    }
}

technique10 Update_Fast
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS_UPDATE() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS_UPDATE_FAST() ) );
		
		SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( DisableDepth, 0 );
        SetRasterizerState( DisableCulling );
    }
}